package com.szl.group.config.aop;


import com.szl.group.common.anno.Idempotent;
import com.szl.group.exception.BusinessException;
import com.szl.group.utils.JoinPointUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;

import static com.szl.group.common.enums.ResultCodeEnum.FAILED;

/**
 * 基于redis + 分布式锁实现幂等性
 * <p>
 * author shenxiaolong
 * date 2020/7/8
 */
@Aspect
@Component
@Order(4)
public class IdempotentAspect {

    /**
     * lua
     */
    private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ValueOperations valueOperations;

    // 增强带有Idempotent注解的方法
    @Pointcut("@annotation(com.szl.group.common.anno.Idempotent)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        // 可以根据业务获取用户唯一的个人信息
        String id = "";

        Idempotent idempotent = JoinPointUtils.getAnnotation(pjp, Idempotent.class);
        String prefix = idempotent.prefix();
        if (StringUtils.isBlank(prefix)) {
            throw new BusinessException(FAILED, "CacheLock prefix can't be null");
        }

        // 拼接 key
        String delimiter = idempotent.delimiter();
        StringBuilder sb = new StringBuilder();
        sb.append(prefix).append(delimiter).append(id);
        final String lockKey = sb.toString();
        final String UUID = java.util.UUID.randomUUID().toString().replaceAll("-", "");

        try {
            // 获取锁
            boolean success =valueOperations.setIfAbsent(lockKey, UUID, idempotent.expire(), idempotent.timeUnit());
            if (!success) {
                throw new BusinessException(FAILED, "请勿重复提交");
            }
            return pjp.proceed();
        } finally {
            // 最后释放锁
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
            redisTemplate.execute(redisScript, Collections.singletonList(lockKey), UUID);
        }

    }
}
